package org.modelcc.io.java.processor;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import org.modelcc.Position;
import org.modelcc.io.java.JavaModelProcessor;
import org.modelcc.io.java.JavaModelReader;
import org.modelcc.io.java.JavaLanguageMetadata;
import org.modelcc.io.java.Reflection;
import org.modelcc.language.metamodel.MemberCollection;
import org.modelcc.language.metamodel.CompositeLanguageElement;
import org.modelcc.language.metamodel.LanguageElement;
import org.modelcc.language.metamodel.LanguageMember;
import org.modelcc.language.metamodel.MemberPositionMetadata;
import org.modelcc.lexer.recognizer.PatternRecognizer;

/**
 * Position processor
 */
public class PositionProcessor extends JavaModelProcessor<JavaLanguageMetadata>
{	
	public PositionProcessor (JavaModelReader reader)
	{
		super(reader);
	}
	
	@Override
	public void process(JavaLanguageMetadata metadata) 
	{
    	Set<LanguageElement> elements = metadata.elements;
    	
    	for (Iterator<LanguageElement> ite = elements.iterator();ite.hasNext();) {
    		LanguageElement elem = ite.next();
    		Map<LanguageMember,MemberPositionMetadata> positions = new HashMap<LanguageMember,MemberPositionMetadata>();
    		if (CompositeLanguageElement.class.isAssignableFrom(elem.getClass())) {
    			CompositeLanguageElement celem = (CompositeLanguageElement)elem;
    			Field[] fl = Reflection.getAllFields(elem.getElementClass());
    			for (int i = 0;i < fl.length;i++) {
    				Field field = fl[i];
    				LanguageMember thisElement = null;
    				LanguageMember otherElement = null;
    				int indexThis;
    				int indexOther;
    				indexThis = searchField(celem.getMembers(),fl[i].getName());    
    				if (indexThis != -1)
    					thisElement = celem.getMembers().get(indexThis);

    				if (field.isAnnotationPresent(Position.class)) {
    					Position positionTag = field.getAnnotation(Position.class);
    					Field otherField = null;
    					for (int j = 0;j < fl.length;j++) {
    						if (fl[j].getName().equals(positionTag.element()))
    							otherField = fl[j];
    					}

    					indexOther = searchField(celem.getMembers(),positionTag.element());    
    					if (indexOther != -1)
    						otherElement = celem.getMembers().get(indexOther);

    					boolean empty = true;
    					if (otherElement != null) {
    						if (otherElement.getPrefix() != null) {
    							for (PatternRecognizer prx : otherElement.getPrefix()) {
    								if (prx.read("",0)==null) {
    									empty = false;
    								}
    							}
    						}
    						if (otherElement.getSuffix() != null) {
    							for (PatternRecognizer prx : otherElement.getSuffix()) {
    								if (prx.read("",0)==null) {
    									empty = false;
    								}
    							}
    						}
    					}

    					if (otherElement==null) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": The @Position annotation refers to an undefined field.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else if (otherElement==thisElement) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": The @Position annotation refers to the same field.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else if (MemberCollection.class.isAssignableFrom(thisElement.getClass()) &&
    							(contains(positionTag.position(),Position.BEFORELAST)||contains(positionTag.position(),Position.WITHIN))) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": The @Position annotation cannot be applied to a list and have BEFORELAST or WITHIN values.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else if (!MemberCollection.class.isAssignableFrom(otherElement.getClass()) &&
    							(contains(positionTag.position(),Position.BEFORELAST)||contains(positionTag.position(),Position.WITHIN))) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": The @Position annotation cannot be applied to BEFORELAST or WITHIN a non-list element.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else if (otherField.isAnnotationPresent(Position.class)) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": The @Position annotation cannot refer to a member annotated with @Position.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else if (otherElement.isOptional()) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": The @Position annotation cannot refer to a member annotated with @Optional.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else if (MemberCollection.class.isAssignableFrom(otherElement.getClass()) &&
    							((MemberCollection)otherElement).getMinimumMultiplicity()==0 && empty) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": The @Position annotation cannot refer to a member annotated with minimum multiplicity 0 and empty prefixes or suffixes.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else if (!compatible(field,fl)) {
    						log(Level.SEVERE, "In field \"{0}\" of class \"{1}\": @Position clashes with another member.", new Object[]{field.getName(),elem.getElementClass().getCanonicalName()});
    					} else {
    						positions.put(thisElement,new MemberPositionMetadata(otherElement,positionTag.position(),positionTag.separatorPolicy()));
    					}
    				}
    			}
    		}
    		elem.setPositions(positions);
    	}		
    }
	
    private boolean contains (int[] haystack, int needle) 
    {
    	for (int i = 0;i < haystack.length;i++)
    		if (haystack[i]==needle)
    			return true;
    	return false;
    }


    private boolean compatible(Field field, Field[] fl) {
    	Position positionTag = field.getAnnotation(Position.class);
    	for (int j = 0;j < fl.length;j++) {
    		if (fl[j] != field) {
    			if (fl[j].isAnnotationPresent(Position.class)) {
    				Position otherPositionTag = fl[j].getAnnotation(Position.class);
    				if (positionTag.element().equals(otherPositionTag.element())) {

    					if ( (contains(positionTag.position(),Position.BEFORE) &&  (contains(otherPositionTag.position(),Position.BEFORE))) ||
    						 (contains(positionTag.position(),Position.AFTER) &&  (contains(otherPositionTag.position(),Position.AFTER))) ||
    						 (contains(positionTag.position(),Position.WITHIN) &&  (contains(otherPositionTag.position(),Position.WITHIN) || contains(otherPositionTag.position(),Position.BEFORELAST))) ||
    						 (contains(positionTag.position(),Position.BEFORELAST) &&  (contains(otherPositionTag.position(),Position.WITHIN) || contains(otherPositionTag.position(),Position.BEFORELAST))) )
    						return false;
    				}
    			}
    		}
    	}
    	return true;
    }

    private int searchField (List<LanguageMember> contents, String name) 
    {
    	int index = 0;
    	while (index<contents.size()) {
    		if (contents.get(index).getID().equals(name)) {
    			return index;
    		}
    		index++;
    	}
    	return -1;
    }
    
}
